/*
 * Copyright 2024 LinQingYing. and contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * The use of this source code is governed by the Apache License 2.0,
 * which allows users to freely use, modify, and distribute the code,
 * provided they adhere to the terms of the license.
 *
 * The software is provided "as-is", and the authors are not responsible for
 * any damages or issues arising from its use.
 *
 */

package com.linqingying.cangjie.psi.psiUtil

import com.intellij.extapi.psi.StubBasedPsiElementBase
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement
import com.intellij.psi.stubs.StubElement
import com.intellij.psi.tree.TokenSet
import com.intellij.psi.util.findTopmostParentInFile
import com.intellij.util.codeInsight.CommentUtilCore
import com.linqingying.cangjie.CjNodeTypes
import com.linqingying.cangjie.lexer.CangJieLexer
import com.linqingying.cangjie.lexer.CjModifierKeywordToken
import com.linqingying.cangjie.lexer.CjTokens
import com.linqingying.cangjie.name.FqName
import com.linqingying.cangjie.name.Name
import com.linqingying.cangjie.name.SpecialNames
import com.linqingying.cangjie.psi.*
import com.linqingying.cangjie.psi.stubs.CangJieTypeStatementStub
import java.util.*

//fun CjDeclaration.isExpectDeclaration(): Boolean =
//    when {
//        hasExpectModifier() -> true
//        this is CjParameter -> ownerFunction?.isExpectDeclaration() == true
//        else -> containingTypeStatement?.isExpectDeclaration() == true
//    }
fun CjTypeStatement.effectiveDeclarations(): List<CjDeclaration> {
    return when (this) {
        is CjStruct -> declarations + primaryConstructorParameters.filter { p -> p.hasLetOrVar() }

        is CjClass -> declarations + primaryConstructorParameters.filter { p -> p.hasLetOrVar() }
        else -> declarations
    }
}

fun getImportedSimpleNameByImportAlias(file: CjFile, aliasName: String): String? {
    val directive = file.findImportByAlias(aliasName) ?: return null

    var reference = directive.importedReference
    while (reference is CjDotQualifiedExpression) {
        reference = reference.selectorExpression
    }
    if (reference is CjSimpleNameExpression) {
        return reference.referencedName
    }

    return null
}

fun CjExpression.lastBlockStatementOrThis(): CjExpression =
    (this as? CjBlockExpression)?.statements?.lastOrNull() ?: this

fun CjFunctionLiteral.findLabelAndCall(): Pair<Name?, CjCallExpression?> {
    val literalParent = (this.parent as CjLambdaExpression).parent

    fun CjValueArgument.callExpression(): CjCallExpression? {
        val parent = parent
        return (if (parent is CjValueArgumentList) parent else this).parent as? CjCallExpression
    }

    when (literalParent) {
//        is CjLabeledExpression -> {
//            val callExpression = (literalParent.parent as? CjValueArgument)?.callExpression()
//            return Pair(literalParent.getLabelNameAsName(), callExpression)
//        }

        is CjValueArgument -> {
            val callExpression = literalParent.callExpression()
            val label = (callExpression?.calleeExpression as? CjSimpleNameExpression)?.referencedNameAsName
            return Pair(label, callExpression)
        }

        else -> {
            return Pair(null, null)
        }
    }
}

// Annotations on labeled expression lies on it's base expression
fun CjExpression.getAnnotationEntries(): List<CjAnnotationEntry> {
    return when (val parent = parent) {
//        is CjAnnotatedExpression -> parent.annotationEntries
//        is CjLabeledExpression -> parent.getAnnotationEntries()
        else -> emptyList()
    }
}

fun CjExpression.getOutermostParenthesizerOrThis(): CjExpression {
    return (parentsWithSelf.zip(parents)).firstOrNull {
        val (element, parent) = it
        when (parent) {
            is CjParenthesizedExpression -> false

            else -> true
        }
    }?.first as CjExpression? ?: this
}

fun CjTypeStatement.isAbstract(): Boolean =
    this is CjInterface || this is CjClass && hasModifier(CjTokens.ABSTRACT_KEYWORD)

fun CjParameter.isPropertyParameter() = ownerFunction is CjPrimaryConstructor && hasLetOrVar()
fun CjSimpleNameExpression.isPackageDirectiveExpression(): Boolean {
    val parent = parent
    return parent is CjPackageDirective || parent.parent is CjPackageDirective
}

fun CjSimpleNameExpression.isImportDirectiveExpression(): Boolean {
    val parent = parent
    return parent is CjImportDirectiveItem || parent!!.parent is CjImportDirectiveItem
}

fun CjSimpleNameExpression.getQualifiedElementOrCallableRef(): CjElement {
    val parent = parent
    if (parent is CjCallableReferenceExpression && parent.callableReference == this) return parent

    return getQualifiedElement()
}

fun CjExpression.getQualifiedExpressionForReceiver(): CjQualifiedExpression? {
    val parent = parent
    return if (parent is CjQualifiedExpression && parent.receiverExpression == this) parent else null
}

private val BAD_NEIGHBOUR_FOR_SIMPLE_TEMPLATE_ENTRY_PATTERN = Regex("([a-zA-Z0-9_]|[^\\p{ASCII}]).*")

/**
 * Returns enclosing qualifying element for given [[CjSimpleNameExpression]]
 * ([[CjQualifiedExpression]] or [[CjUserType]] or original expression)
 */
fun CjSimpleNameExpression.getQualifiedElement(): CjElement {
    val baseExpression = (parent as? CjCallExpression) ?: this
    return when (val parent = baseExpression.parent) {
        is CjQualifiedExpression -> if (parent.selectorExpression == baseExpression) parent else baseExpression
        is CjUserType -> if (parent.referenceExpression == baseExpression) parent else baseExpression
        else -> baseExpression
    }
}

fun CjModifierListOwner.isPrivate(): Boolean = hasModifier(CjTokens.PRIVATE_KEYWORD)

fun canPlaceAfterSimpleNameEntry(element: PsiElement?): Boolean {
    val entryText = element?.text ?: return true
    return !BAD_NEIGHBOUR_FOR_SIMPLE_TEMPLATE_ENTRY_PATTERN.matches(entryText)
}

fun CjSimpleNameExpression.getReceiverExpression(): CjExpression? {
    val parent = parent
    when {
        parent is CjQualifiedExpression -> {
            val receiverExpression = parent.receiverExpression
            // Name expression can't be receiver for itself
            if (receiverExpression != this) {
                return receiverExpression
            }
        }

        parent is CjCallExpression -> {
            //This is in case `a().b()`
            val grandParent = parent.parent
            if (grandParent is CjQualifiedExpression) {
                val parentsReceiver = grandParent.receiverExpression
                if (parentsReceiver != parent) {
                    return parentsReceiver
                }
            }
        }
//        parent is CjBinaryExpression && parent.operationReference == this -> {
//            return if (parent.operationToken in OperatorConventions.IN_OPERATIONS) parent.right else parent.left
//        }
        parent is CjUnaryExpression && parent.operationReference == this -> {
            return parent.baseExpression
        }

        parent is CjUserType -> {
            val qualifier = parent.qualifier
            if (qualifier != null) {
                return qualifier.referenceExpression!!
            }
        }
    }

    return null
}

fun PsiElement.isFunctionalExpression(): Boolean = this is CjNamedFunction && nameIdentifier == null
fun CjElement.containingClass(): CjClass? = getStrictParentOfType()

fun CjSimpleNameExpression.getTopmostParentQualifiedExpressionForSelector(): CjQualifiedExpression? {
    return generateSequence<CjExpression>(this) {
        val parentQualified = it.parent as? CjQualifiedExpression
        if (parentQualified?.selectorExpression == it) parentQualified else null
    }.last() as? CjQualifiedExpression
}

fun CjModifierListOwner.visibilityModifier() = modifierList?.modifierFromTokenSet(CjTokens.VISIBILITY_MODIFIERS)
private fun CjModifierList.modifierFromTokenSet(set: TokenSet): PsiElement? {
    return set.types
        .asSequence()
        .map { getModifier(it as CjModifierKeywordToken) }
        .firstOrNull { it != null }

}

fun CjModifierListOwner.visibilityModifierType(): CjModifierKeywordToken? =
    visibilityModifier()?.node?.elementType as CjModifierKeywordToken?

fun CjExpression.referenceExpression(): CjReferenceExpression? =
    (if (this is CjCallExpression) calleeExpression else this) as? CjReferenceExpression

fun CjNamedFunction.isContractPresentPsiCheck(isAllowedOnMembers: Boolean): Boolean {
    val contractAllowedHere =
        (isAllowedOnMembers || isTopLevel) &&
                hasBlockBody() &&
                !hasModifier(CjTokens.OPERATOR_KEYWORD)
    if (!contractAllowedHere) return false

    val firstExpression = (this as? CjFunction)?.bodyBlockExpression?.statements?.firstOrNull() ?: return false

    return firstExpression.isContractDescriptionCallPsiCheck()
}

fun CjExpression.isContractDescriptionCallPsiCheck(): Boolean =
    (this is CjCallExpression && calleeExpression?.text == "contract") || (this is CjQualifiedExpression && isContractDescriptionCallPsiCheck())

fun <D> visitChildren(element: CjElement, visitor: CjVisitor<Void, D>, data: D) {
    var child = element.firstChild
    while (child != null) {
        if (child is CjElement) {
            child.accept(visitor, data)
        }
        child = child.nextSibling
    }

}

private fun StubElement<*>.collectAnnotationEntriesFromStubElement(): List<CjAnnotationEntry> {
    return childrenStubs.flatMap { child ->
        when (child.stubType) {
            CjNodeTypes.ANNOTATION_ENTRY -> listOf(child.psi as CjAnnotationEntry)

            else -> emptyList()
        }
    }
}

private fun CjAnnotationsContainer.collectAnnotationEntriesFromPsi(): List<CjAnnotationEntry> {
    return children.flatMap { child ->
        when (child) {
            is CjAnnotationEntry -> listOf(child)

            else -> emptyList()
        }
    }
}

fun CjAnnotationsContainer.collectAnnotationEntriesFromStubOrPsi(): List<CjAnnotationEntry> {
    return when (this) {
        is StubBasedPsiElementBase<*> -> stub?.collectAnnotationEntriesFromStubElement()
            ?: collectAnnotationEntriesFromPsi()

        else -> collectAnnotationEntriesFromPsi()
    }
}

fun StubBasedPsiElementBase<out CangJieTypeStatementStub<out CjTypeStatement>>.getSuperNames(): List<String> {
    fun addSuperName(result: MutableList<String>, referencedName: String) {
        result.add(referencedName)

        val file = containingFile
        if (file is CjFile) {
            val directive = file.findImportByAlias(referencedName)
            if (directive != null) {
                var reference = directive.importedReference
                while (reference is CjDotQualifiedExpression) {
                    reference = reference.selectorExpression
                }
                if (reference is CjSimpleNameExpression) {
                    result.add(reference.referencedName)
                }
            }
        }
    }

    require(this is CjTypeStatement) { "it should be ${CjTypeStatement::class} but it is a ${this::class.java.name}" }

    val stub = stub
    if (stub != null) {
        return stub.getSuperNames()
    }

    val specifiers = this.superTypeListEntries
    if (specifiers.isEmpty()) return Collections.emptyList()

    val result = ArrayList<String>()
    for (specifier in specifiers) {
        val superType = specifier.typeAsUserType
        if (superType != null) {
            val referencedName = superType.referencedName
            if (referencedName != null) {
                addSuperName(result, referencedName)
            }
        }
    }

    return result
}

fun isComment(element: PsiElement): Boolean {
    return CommentUtilCore.isComment(element)
}

fun CjEnumEntry.safeFqNameForLazyResolveByParent(): FqName? {
    //应该只为包级声明创建特殊名称，这样就可以安全地依赖于父级的真实fq名称
    val parentFqName = (this.parent?.parent as CjEnum).safeFqNameForLazyResolve()
    return parentFqName?.child(safeNameForLazyResolve())
}

fun CjEnumEntry.safeFqNameForLazyResolve(): FqName? {
    //应该只为包级声明创建特殊名称，这样就可以安全地依赖于父级的真实fq名称
    val parentFqName = CjNamedDeclarationUtil.getParentFqName(this.parent?.parent as CjEnum)
    return parentFqName?.child(safeNameForLazyResolve())
}
fun CjNamedDeclaration.safeFqNameForLazyResolve(name:Name): FqName? {
    //应该只为包级声明创建特殊名称，这样就可以安全地依赖于父级的真实fq名称
    val parentFqName = CjNamedDeclarationUtil.getParentFqName(this)
    return parentFqName?.child(name)
}
fun CjNamedDeclaration.safeFqNameForLazyResolve(name:String?): FqName? {
    //应该只为包级声明创建特殊名称，这样就可以安全地依赖于父级的真实fq名称
    val parentFqName = CjNamedDeclarationUtil.getParentFqName(this)
    return parentFqName?.child(Name.identifier(name ?: ""))
}
fun CjNamedDeclaration.safeFqNameForLazyResolve(): FqName? {
    //应该只为包级声明创建特殊名称，这样就可以安全地依赖于父级的真实fq名称
    val parentFqName = CjNamedDeclarationUtil.getParentFqName(this)
    return parentFqName?.child(safeNameForLazyResolve())
}

fun CjNamedDeclaration.safeNameForLazyResolve(): Name {
    return nameAsName.safeNameForLazyResolve()
}
//fun CjFunctionImpl.isContractPresentPsiCheck(isAllowedOnMembers: Boolean): Boolean {
//    val contractAllowedHere =
//        (isAllowedOnMembers || isTopLevel) &&
//                hasBlockBody() &&
//                !hasModifier(CjTokens.OPERATOR_KEYWORD)
//    if (!contractAllowedHere) return false
//
//    val firstExpression = (this as? CjFunction)?.bodyBlockExpression?.statements?.firstOrNull() ?: return false
//
//    return firstExpression.isContractDescriptionCallPsiCheck()
//}
//fun CjExpression.isContractDescriptionCallPsiCheck(): Boolean =
//    (this is CjCallExpression && calleeExpression?.text == "contract") || (this is CjQualifiedExpression && isContractDescriptionCallPsiCheck())

fun Name?.safeNameForLazyResolve(): Name = this?.takeUnless(Name::isSpecial) ?: SpecialNames.NO_NAME_PROVIDED


fun CjExpression.getAssignmentByLHS(): CjBinaryExpression? {
    val parent = parent as? CjBinaryExpression ?: return null
    return if (CjPsiUtil.isAssignment(parent) && parent.left == this) parent else null
}

fun CjExpression.getQualifiedExpressionForSelectorOrThis(): CjExpression {
    return getQualifiedExpressionForSelector() ?: this
}

inline fun <reified T : PsiElement> T.copied(): T {
    return copy() as T
}

fun getTrailingCommaByClosingElement(closingElement: PsiElement?): PsiElement? {
    val elementBeforeClosingElement =
        closingElement?.getPrevSiblingIgnoringWhitespaceAndComments() ?: return null

    return elementBeforeClosingElement.run { if (node.elementType == CjTokens.COMMA) this else null }
}

val CjQualifiedExpression.callExpression: CjCallExpression?
    get() = selectorExpression as? CjCallExpression

var CjElement.parentSubstitute: PsiElement? by UserDataProperty(Key.create<PsiElement>("PARENT_SUBSTITUTE"))
fun String.quoteIfNeeded(): String = if (this.isIdentifier()) this else "`$this`"
fun String?.isIdentifier(): Boolean {
    if (this == null || isEmpty()) return false

    val lexer = CangJieLexer()
    lexer.start(this, 0, length)
    if (lexer.tokenType !== CjTokens.IDENTIFIER) return false
    lexer.advance()
    return lexer.tokenType == null
}

fun PsiElement.astReplace(newElement: PsiElement) = parent.node.replaceChild(node, newElement.node)
fun CjElement.getQualifiedElementSelector(): CjElement? {
    return when (this) {
        is CjSimpleNameExpression -> this
        is CjCallExpression -> calleeExpression
        is CjQualifiedExpression -> {
            val selector = selectorExpression
            (selector as? CjCallExpression)?.calleeExpression ?: selector
        }

        is CjUserType -> referenceExpression
        else -> null
    }
}

val CjTypeElement.qualifier: CjTypeElement?
    get() {
        return when (this) {
            is CjUserType -> this.qualifier

            else -> null
        }
    }

fun CjElement.getQualifiedExpressionForSelector(): CjQualifiedExpression? {
    val parent = parent
    return if (parent is CjQualifiedExpression && parent.selectorExpression == this) parent else null
}

val CjDeclaration.containingTypeStatement: CjTypeStatement?
    get() = parent.let {
        when (it) {
            is CjAbstractClassBody -> it.parent as? CjTypeStatement
            is CjTypeStatement -> it
            is CjParameterList -> (it.parent as? CjPrimaryConstructor)?.getContainingTypeStatement()
            else -> null
        }
    }

fun getTrailingCommaByElementsList(elementList: PsiElement?): PsiElement? {
    val lastChild =
        elementList?.lastChild?.let { if (it !is PsiComment) it else it.getPrevSiblingIgnoringWhitespaceAndComments() }
    return lastChild?.takeIf { it.node.elementType == CjTokens.COMMA }
}

fun CjStringTemplateExpression.getContentRange(): TextRange {
    val start = node.firstChildNode.textLength
    val lastChild = node.lastChildNode
    val length = textLength
    return TextRange(
        start,
        if (lastChild.elementType == CjTokens.CLOSING_QUOTE) length - lastChild.textLength else length
    )
}

fun CjStringTemplateExpression.isSingleQuoted(): Boolean = node.firstChildNode.textLength == 1
fun CjStringTemplateExpression.isPlain() = entries.all { it is CjLiteralStringTemplateEntry }


fun List<CangJieImportField>.addIf(element: CangJieImportField) {
    if (!this.contains(element)) {
        this + element // 如果不存在，则返回新列表
    }
}

fun CjDeclaration.modalityModifier() = modifierFromTokenSet(CjTokens.MODALITY_MODIFIERS)
private fun CjModifierListOwner.modifierFromTokenSet(set: TokenSet) = modifierList?.modifierFromTokenSet(set)


fun CjElement.findElementOfAdditionalResolve(): CjElement? {

    val elementOfAdditionalResolve = findTopmostParentInFile {
        it is CjFunction ||
                it is CjAnonymousInitializer ||
//                    it is CjPrimaryConstructor ||
//                    it is CjSecondaryConstructor ||
                it is CjProperty ||
                it is CjVariable ||
                it is CjSuperTypeList ||

                it is CjImportList ||
                it is CjAnnotationEntry ||
                it is CjTypeParameter ||
                it is CjTypeConstraint ||
                it is CjPackageDirective ||
                it is CjCodeFragment ||
                it is CjTypeAlias ||
                it is CjDestructuringDeclaration
    } as CjElement?

    when (elementOfAdditionalResolve) {
        null -> {

            if (this is CjAnnotationEntry) {
                return this
            }

            return null
        }

        is CjPackageDirective -> return this
        is CjDeclaration -> {
            if (this is CjParameterBase && !CjPsiUtil.isLocal(this)) {
                return null
            }
            return elementOfAdditionalResolve
        }

        else -> return elementOfAdditionalResolve
    }
}
